import datetime
import json
import os
import re
import socket
import subprocess
import sys
import time
import winreg
from pathlib import Path

import openpyxl
import xlwt

import pymysql
import requests
from PyQt5 import QtGui, QtCore, QtWidgets
from PyQt5.QtCore import Qt, pyqtSignal, QDateTime, QUrl
from PyQt5.QtCore import QTimer
from PyQt5.QtGui import QDesktopServices, QStandardItem, QStandardItemModel, QFont
from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox, QLabel, QAction, QDialog, QFormLayout, \
    QDateTimeEdit, QSpinBox, QDialogButtonBox, QComboBox, QVBoxLayout, QRadioButton, QTreeView, QHBoxLayout, QTextEdit, QPushButton, QGridLayout, QFileDialog, QTableWidgetItem
from openpyxl.workbook import Workbook
from playwright.sync_api import sync_playwright

from Util.CheckVersionThread import CheckVersionThread
from Util.DbProcess import get_user_ip
from Util.DownloadThread import DownloadThread
from Util.LocalDBManager import LocalDBManager
from Util.Login_data import EditStoreDialog
from Util.QwDbManager import QwDbManager
from Util.WorkerThread import WorkerThread
from Util.WorkerThreadQW import WorkerThreadQw
from Util.logging_config import setup_logger
from Util.zconfig import get_setting

from main_window import Ui_MainWindow  # 导入生成的 UI 文件
from qt_material import apply_stylesheet
from login_dialog import LoginDialog

import warnings

warnings.filterwarnings("ignore", category=DeprecationWarning)

class UpdateDialog(QDialog):
    def __init__(self, version, curr_version, update_log, download_url, parent=None):
        super(UpdateDialog, self).__init__(parent)
        self.download_url = download_url  # 记录下载链接
        self.setWindowTitle("版本更新提示")
        self.setFixedSize(420, 320)  # 稍微加大窗口，留出间距
        self.setStyleSheet("background-color: #f9f9f9; border-radius: 8px;")  # 设置背景色和圆角

        # **版本信息**
        version_label = QLabel(f"<b>最新版本：</b> <span style='color: red;'>{version}</span>")
        curr_version_label = QLabel(f"<b>当前版本：</b> <span style='color: blue;'>{curr_version}</span>")
        version_label.setFont(QFont("Arial", 10))
        curr_version_label.setFont(QFont("Arial", 10))

        # **下载链接**
        download_label = QLabel(f'<a href="{download_url}">点击前往下载最新版本</a>')
        download_label.setOpenExternalLinks(True)
        download_label.setFont(QFont("Arial", 10))
        download_label.setStyleSheet("color: #0078D7;")  # 设置链接颜色

        # **更新内容**
        update_text = QTextEdit()
        update_text.setText(update_log)
        update_text.setReadOnly(True)
        update_text.setFont(QFont("Arial", 9))
        update_text.setStyleSheet("""
            border: 1px solid #ccc; 
            border-radius: 5px; 
            padding: 5px; 
            background-color: #fff;
        """)

        # **确认按钮**
        ok_button = QPushButton("确定")
        ok_button.setFont(QFont("Arial", 10, QFont.Bold))
        ok_button.setStyleSheet("""
            QPushButton {
                background-color: #0078D7; 
                color: white; 
                padding: 8px 16px;
                border-radius: 8px; 
                border: 2px solid #005bb5;
            }
            QPushButton:hover {
                background-color: #005bb5;
            }
            QPushButton:pressed {
                background-color: #003f7f;
                border: 2px solid #002a5e;
                padding: 7px 15px;  /* 轻微缩小 */
            }
        """)
        ok_button.clicked.connect(self.on_confirm_clicked)

        # **布局**
        grid_layout = QGridLayout()
        grid_layout.addWidget(curr_version_label, 0, 0)
        grid_layout.addWidget(version_label, 0, 1)

        vbox = QVBoxLayout()
        vbox.addLayout(grid_layout)
        vbox.addWidget(download_label)
        vbox.addWidget(update_text)
        vbox.addWidget(ok_button, alignment=Qt.AlignCenter)

        self.setLayout(vbox)

    def on_confirm_clicked(self):
        """点击确定时跳转到下载页面"""
        QDesktopServices.openUrl(QUrl(self.download_url))  # 打开下载链接
        self.accept()  # 关闭对话框

class StoreSelectionDialog(QDialog):
    def __init__(self, store_names, parent=None):
        super().__init__(parent)
        self.setWindowTitle("选择店铺")

        # 创建布局
        layout = QVBoxLayout()

        # 创建 QComboBox 下拉框
        self.combo_box = QComboBox()

        # 将店铺信息填充到下拉框
        for store in store_names:
            # 假设 store 是一个元组，格式为 (id, platform, name, is_active)
            store_info = f"{store[0]}. {store[2]}"  # 显示id和店铺名称
            self.combo_box.addItem(store_info, store[0])  # 第二个参数是店铺的 id

        # 将下拉框添加到布局中
        layout.addWidget(QLabel("请选择要保存登录数据的店铺:"))
        layout.addWidget(self.combo_box)

        # 创建按钮
        self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        # 修改按钮文本为中文
        self.button_box.button(QDialogButtonBox.Ok).setText("确定")
        self.button_box.button(QDialogButtonBox.Cancel).setText("取消")
        self.button_box.accepted.connect(self.accept)
        self.button_box.rejected.connect(self.reject)

        # 将按钮添加到布局中
        layout.addWidget(self.button_box)

        self.setLayout(layout)

    def get_selected_store(self):
        # 获取用户选择的店铺的 id 和名称
        store_id = self.combo_box.currentData()  # 获取下拉框选项对应的 ID
        store_name = self.combo_box.currentText()  # 获取下拉框显示的店铺名称
        return store_id, store_name

class TreeDialog(QDialog):
    def __init__(self, data, parent=None):
        super().__init__(parent)
        self.setWindowTitle("最近7天任务状态")

        # 创建布局
        layout = QVBoxLayout()

        # 创建 QTreeView
        self.tree_view = QTreeView(self)
        layout.addWidget(self.tree_view)

        # 创建按钮框
        button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, self)
        button_box.button(QDialogButtonBox.Ok).setText("确定")
        button_box.button(QDialogButtonBox.Cancel).setText("取消")
        button_box.accepted.connect(self.accept)
        button_box.rejected.connect(self.reject)
        layout.addWidget(button_box)

        # 设置弹窗的布局
        self.setLayout(layout)

        # 设置弹窗的初始大小
        self.resize(800, 600)  # 800宽, 600高， 可以根据需要调整

        # 加载数据到树形视图
        self.load_data(data)

    def load_data(self, data):
        # 创建树形模型
        model = QStandardItemModel()
        model.setHorizontalHeaderLabels(['任务层级', '状态'])
        root_item = model.invisibleRootItem()

        # 遍历数据并插入到模型中
        self.populate_tree(data, root_item)

        # 将模型设置到 tree_view
        self.tree_view.setModel(model)

        # 设置列宽
        self.tree_view.setColumnWidth(0, 250)  # 设置 "Key" 列宽为 250px
        self.tree_view.setColumnWidth(1, 250)  # 设置 "Value" 列宽为 250px

    def populate_tree(self, data, parent_item):
        """递归将字典数据填充到树形结构中"""
        if isinstance(data, dict):
            for key, value in data.items():
                # 创建一个新的项（节点）
                item = QStandardItem(key)
                item.setEditable(False)  # 设置为不可编辑

                # 如果值是字典，继续递归
                if isinstance(value, dict):
                    child_item = QStandardItem('')  # 创建空的子项
                    child_item.setEditable(False)  # 设置为不可编辑
                    parent_item.appendRow([item, child_item])

                    # 递归填充子节点
                    self.populate_tree(value, item)

                    # 递归检查字典中的布尔值，确保全部为True
                    if self.are_all_true(value):
                        child_item.setText("True")
                    else:
                        child_item.setText("False")

                elif isinstance(value, bool):
                    # 如果值是布尔值，直接显示
                    child_item = QStandardItem(str(value))  # 使用布尔值的字符串
                    child_item.setEditable(False)
                    parent_item.appendRow([item, child_item])
                else:
                    # 对于其他类型的值（非布尔和字典）
                    child_item = QStandardItem(str(value))  # 直接显示值
                    child_item.setEditable(False)
                    parent_item.appendRow([item, child_item])
        else:
            # 如果不是字典，则直接显示数据
            item = QStandardItem(str(data))
            item.setEditable(False)
            parent_item.appendRow([item])

    def are_all_true(self, value):
        """递归检查字典中的布尔值是否全部为 True"""
        if isinstance(value, dict):
            # 如果是字典，递归检查
            return all(self.are_all_true(val) for val in value.values())
        elif isinstance(value, bool):
            return value  # 如果是布尔值，直接返回其值
        return False  # 其他类型直接返回 False

class TimerDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("设置定时任务")
        # self.setGeometry(200, 200, 300, 200)

        # 创建布局
        layout = QFormLayout()

        # 创建日期时间选择控件
        self.start_time_edit = QDateTimeEdit(QDateTime.currentDateTime(), self)
        self.start_time_edit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
        layout.addRow("定时任务启动时间", self.start_time_edit)

        # 创建运行间隔选择控件
        self.interval_spinbox = QSpinBox(self)
        self.interval_spinbox.setRange(1, 1440)  # 1分钟到1440分钟（即24小时）
        self.interval_spinbox.setValue(120)  # 默认30分钟
        layout.addRow("定时任务运行间隔(分钟)", self.interval_spinbox)

        # 创建浏览器选择部分
        self.browser_layout = QVBoxLayout()
        self.chrome_radio = QRadioButton("Google Chrome", self)
        self.edge_radio = QRadioButton("Microsoft Edge", self)

        # 默认选择谷歌浏览器
        self.chrome_radio.setChecked(True)
        self.browser_layout.addWidget(self.chrome_radio)
        self.browser_layout.addWidget(self.edge_radio)
        layout.addRow("选择浏览器", self.browser_layout)

        # 创建随机等待时间控件
        random_wait_layout = QHBoxLayout()

        self.min_wait_label = QLabel("最小随机等待时间(秒):", self)
        self.min_wait_spinbox = QSpinBox(self)
        self.min_wait_spinbox.setRange(2, 3600)  # 最小等待时间范围为1秒到3600秒
        self.min_wait_spinbox.setValue(2)  # 默认1秒

        self.max_wait_label = QLabel("最大随机等待时间(秒):", self)
        self.max_wait_spinbox = QSpinBox(self)
        self.max_wait_spinbox.setRange(2, 3600)  # 最大等待时间范围为1秒到3600秒
        self.max_wait_spinbox.setValue(5)  # 默认5秒

        # 将最小和最大等待时间控件添加到同一行
        random_wait_layout.addWidget(self.min_wait_label)
        random_wait_layout.addWidget(self.min_wait_spinbox)
        random_wait_layout.addWidget(self.max_wait_label)
        random_wait_layout.addWidget(self.max_wait_spinbox)

        layout.addRow(random_wait_layout)

        # 创建按钮框
        button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel, self)
        button_box.button(QDialogButtonBox.Ok).setText("确定")
        button_box.button(QDialogButtonBox.Cancel).setText("取消")
        button_box.accepted.connect(self.accept)
        button_box.rejected.connect(self.reject)
        layout.addWidget(button_box)

        # 设置弹窗的布局
        self.setLayout(layout)

    def get_selected_browser(self):
        """获取用户选择的浏览器"""
        if self.chrome_radio.isChecked():
            return "chrome"
        elif self.edge_radio.isChecked():
            return "msedge"
        else:
            return "chrome"

    def get_start_time(self):
        return self.start_time_edit.dateTime()

    def get_interval(self):
        return self.interval_spinbox.value()

    def get_wait_time(self):
        return self.min_wait_spinbox.value(),self.max_wait_spinbox.value()

class SimpleApp(QMainWindow, Ui_MainWindow):
    log_signal = pyqtSignal(str)

    def __init__(self):
        super().__init__()
        self.zdebug = True  # 默认是开发机
        self.version_thread = None
        self.notice = '更新内容'
        self.weburl = ''#网站地址
        self.version = '' #AutoGet最新版本
        self.currVersion = '4.9.15' #AutoGet当前版本
        self.min_wait_time = 2 #最小随机等待时间
        self.max_wait_time = 5 #最大随机等待时间
        self.downThread = None #下载 依赖库
        self.selected_ids = []#已选中的店铺id列表
        self.dburl = None#数据库连接地址
        self.selected_browser = 'Google'
        self.phone = None
        self.url = None
        self.tb_timer_start_time = None#自动任务下次运行时间
        self.next_txt = None
        self.store_names = None
        self.logger = None
        self.timer = None
        self.page = None
        self.context = None
        self.browser = None
        self.playwright = None
        self.work = None  # 初始化工作线程为 None

        self.setupUi(self)
        self.setup_logging()
        self.log_signal.connect(self.up_log)
        apply_stylesheet(self, theme='light_blue.xml', invert_secondary=True)
        self.load_log_file()
        self.db = LocalDBManager(self.logger)  # 全局本地数据库对象,拼多多登录数据
        self.qwdb = QwDbManager()  # 企微审批工具专用数据库管理类
        self.login_data = self.db.get_all_stores_dict()
        self.tb_next_date.setStyleSheet("color: black;")
        # 确保目录存在
        Path(r'C:\new\AutomationProfile').mkdir(parents=True, exist_ok=True)
        #====================================================== 设置窗口标题,版本信息=========================================================================
        versionTitle = 'AutoGET数据获取工具 V'+ self.currVersion
        self.setWindowTitle(versionTitle)
        # 帮助文档
        open_web_action = QAction('📒 帮助文档', self)
        open_web_action.triggered.connect(self.open_web)
        self.toolBar.addAction(open_web_action)
        # 插件解压==>取消使用插件
        # CRX_action = QAction('插件解压', self)
        # CRX_action.setToolTip('已停用-解压后的文件放置路径为C:/new/AutomationProfile/AutoGET/')  # 鼠标悬浮提示
        # CRX_action.triggered.connect(self.open_html)
        # self.toolBar.addAction(CRX_action)
        # 下载视觉识别支持库
        OCR_action = QAction('📥 下载依赖库', self)
        OCR_action.setToolTip('下载后的文件放置路径为AutoGet所在文件夹的download/OCR下')  # 鼠标悬浮提示
        OCR_action.triggered.connect(self.start_download)
        self.toolBar.addAction(OCR_action)
        # 维护登录数据库
        login_action = QAction('🎫 登录信息', self)
        login_action.setToolTip('登录信息保存在AutoGet所在文件夹的cookies文件夹下')  # 鼠标悬浮提示
        login_action.triggered.connect(self.save_login_data)
        self.toolBar.addAction(login_action)
        # ====================================================== 设置窗口状态栏=========================================================================
        self.statusBar.showMessage("准备就绪")

    def open_web(self):
        '''
        打开在线版帮助文档
        :return:
        '''
        url = QUrl("https://kdocs.cn/l/cqbPKzihNPE4")
        QDesktopServices.openUrl(url)

    def open_html(self):
        '''
        打开插件解压网址
        :return:
        '''
        url = QUrl("https://crxextractor.com/")
        QDesktopServices.openUrl(url)

    def start_download(self):
        '''
        下载依赖库
        :return:
        '''
        self.logger.info("开始检查依赖库...")
        self.downThread = DownloadThread(self.logger)
        self.downThread.progress_signal.connect(self.up_log)
        self.downThread.status_signal.connect(self.update_status)
        self.downThread.start()

    def save_login_data(self):
        if self.store_names is None:
            QMessageBox.critical(self, '错误', '未获取最新店铺数据!')
        else:
            self.storeDialog = EditStoreDialog(self.db,self.store_names,self.login_data, self)
            self.storeDialog.exec_()

    def load_log_file(self):
        self.zdebug = get_setting('DEBUG')
        if self.zdebug:
            self.dburl = get_setting('DBURL')['DEV']
            self.weburl = get_setting('WEBURL')['DEV']
        else:
            self.dburl = get_setting('DBURL')['PRD']
            self.weburl = get_setting('WEBURL')['PRD']

        # **启动版本检查线程**
        self.version_thread = CheckVersionThread(self.dburl)
        self.version_thread.version_checked.connect(self.on_version_checked)
        self.version_thread.start()
        zbase_dir = os.getcwd()
        now = datetime.datetime.now()
        today = now.strftime('%Y-%m-%d')
        log_file = 'log/autoTasklog/tb/' + today + '.log'
        zauto_task_log_folder = os.path.join(zbase_dir, log_file)  # 日志存放的目录
        if os.path.exists(zauto_task_log_folder):
            try:
                with open(zauto_task_log_folder, 'r', encoding='utf-8') as file:  # 尝试使用 UTF-8 编码
                    log_content = file.read()
                    self.tb_log.setPlainText(log_content)
            except UnicodeDecodeError:  # 捕获解码错误
                try:
                    with open(zauto_task_log_folder, 'r', encoding='gbk') as file:  # 尝试使用 GBK 编码
                        log_content = file.read()
                        self.tb_log.setPlainText(log_content)
                except Exception as e:
                    self.tb_log.setPlainText(f"未读取到今天的日志文件")
        else:
            self.tb_log.setPlainText(f"未读取到今天的日志文件")
            # 使用 QTimer 延迟滚动到最后一行
        QTimer.singleShot(100, lambda: self.tb_log.verticalScrollBar().setValue(self.tb_log.verticalScrollBar().maximum()))

    def on_version_checked(self, version, notice, bot_url):
        """ 版本查询结果返回，更新 UI """
        self.version = version
        self.notice = notice
        self.url = bot_url

        if not self.zdebug and self.version != self.currVersion:
            dialog = UpdateDialog(
                version=self.version,
                curr_version=self.currVersion,
                update_log=self.notice,
                download_url=self.weburl + '/index/file_list.html',
                parent=self
            )
            if dialog.exec_() == QDialog.Rejected:
                print("用户关闭了更新窗口，主窗口继续运行")

    def open_url(self):
        """打开目标网址"""
        QDesktopServices.openUrl(QUrl(self.weburl_down))

    def send_wechat_message(self, message):
        pattern = r'(\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\]) - (.*)'
        match = re.match(pattern, message)
        if match:
            timestamp = match.group(1)  # 日期时间部分
            content = match.group(2)  # 剩余内容部分
        else:
            timestamp = datetime.datetime.now().strftime('%Y-%m-%d HH:MM:SS')
            content = '请查看AutoGET运行日志'
        txt = content.split('-')

        zcolor = 'red'
        payload = {
            "msgtype": "text",
            "text": {
                "content": '',
                "mentioned_mobile_list": self.phone
            }
        }
        if not self.url is None:
            response = requests.post(self.url, json=payload)

        payload = {
            "msgtype": "markdown",
            "markdown": {
                "content": f"""自动下载任务<font color="{zcolor}">错误</font>，
                    > 时间:<font color="comment">{timestamp}</font>
                    > 错误详情:<font color="comment">{content}</font>"""
            }
        }
        if not self.url is None:
            response = requests.post(self.url, json=payload)
        return response.json()

    def update_status(self, message):
        self.statusBar.showMessage(message)

    def up_log(self, message):
        self.tb_log.append(message)
        if 'ERROR' in message:
            response = self.send_wechat_message(message)

    def setup_logging(self):
        base_dir = os.getcwd()
        auto_task_log_folder = os.path.join(base_dir, 'log/autoTasklog/tb')
        if not os.path.exists(auto_task_log_folder):
            os.makedirs(auto_task_log_folder)
        self.logger = setup_logger("app_logger", auto_task_log_folder, self.log_signal)

    def tb_store_btn(self):
        try:
            if self.zdebug:
                dburl = get_setting('DBURL')['DEV']
            else:
                dburl = get_setting('DBURL')['PRD']

            connection = pymysql.connect(
                host=dburl,
                user='root',
                password='123',
                database='ecadb'
            )
            cursor = connection.cursor()

            # 执行 SQL 查询
            sql_query = """SELECT id, store_type, name, auto_fetch_data FROM ecapp_storename"""
            cursor.execute(sql_query)
            self.store_names = cursor.fetchall()

            # 创建树形模型
            self.model = QtGui.QStandardItemModel()
            self.model.setHorizontalHeaderLabels(["店铺分类"])

            # 存储父级节点
            self.platform_dict = {}

            for id, store_type, name, auto_fetch in self.store_names:
                if store_type not in self.platform_dict:
                    parent_item = QtGui.QStandardItem(store_type)
                    parent_item.setCheckable(True)
                    parent_item.setEditable(False)
                    parent_item.setCheckState(QtCore.Qt.Unchecked)  # 初始状态未选中
                    self.platform_dict[store_type] = parent_item
                    self.model.appendRow(parent_item)

                # 创建子项（店铺）
                if auto_fetch == 1:
                    store = f'√ {id}. {store_type}-{name}'
                else:
                    store = f'× {id}. {store_type}-{name}'
                store_item = QtGui.QStandardItem(store)
                store_item.setCheckable(True)
                store_item.setEditable(False)
                self.platform_dict[store_type].appendRow(store_item)

            # 设置模型
            self.tb_list_view.setModel(self.model)
            self.tb_list_view.setHeaderHidden(True)  # 隐藏表头

            # 监听选中事件，确保父子节点联动
            self.model.itemChanged.connect(self.update_children)

            # **监听点击事件（无论点文本还是多选框，都能切换选中状态）**
            self.tb_list_view.clicked.connect(self.toggle_check_state)

            cursor.close()
            connection.close()
            self.logger.info('已获取最新店铺数据')
            self.login_data = self.db.get_all_stores_dict()

        except BaseException as e:
            self.logger.info(f'连接数据库失败: {str(e)}')

    def update_children(self, item):
        """处理父子节点的选中联动"""
        if not item.isCheckable():  # 确保是可选项
            return
        self.model.itemChanged.disconnect(self.update_children)  # 临时禁用信号，防止递归触发
        if item.hasChildren():  # 处理父节点（平台）
            for row in range(item.rowCount()):
                child = item.child(row)
                child.setCheckState(item.checkState())
        else:  # 处理子节点（店铺）
            parent = item.parent()
            if parent:
                checked_count = sum(parent.child(row).checkState() == QtCore.Qt.Checked for row in range(parent.rowCount()))
                if checked_count == parent.rowCount():
                    parent.setCheckState(QtCore.Qt.Checked)
                elif checked_count == 0:
                    parent.setCheckState(QtCore.Qt.Unchecked)
                else:
                    parent.setCheckState(QtCore.Qt.PartiallyChecked)
        self.model.itemChanged.connect(self.update_children)  # 重新启用信号

    def toggle_check_state(self, index):
        """点击文本时，手动切换选中状态"""
        item = self.model.itemFromIndex(index)
        if item and item.isCheckable():
            # 获取点击位置，判断是否点击了文本
            rect = self.tb_list_view.visualRect(index)  # 获取索引所在的可视区域
            pos = self.tb_list_view.viewport().mapFromGlobal(QtGui.QCursor.pos())  # 获取鼠标点击位置（全局坐标 -> 视图坐标）

            # 计算多选框的区域（大约在左侧 16px 内）
            checkbox_area = QtCore.QRect(rect.left(), rect.top(), 16, rect.height())

            if not checkbox_area.contains(pos):  # 只有点击文本时，才手动切换选中状态
                new_state = QtCore.Qt.Unchecked if item.checkState() == QtCore.Qt.Checked else QtCore.Qt.Checked
                item.setCheckState(new_state)

    def ignore_double_click(self, event):
        event.ignore()  # 忽略双击事件

    def get_chrome_path(self):
        try:
            # 打开注册表键,先找当前用户的注册表,没有再找全局注册表
            with winreg.OpenKey(winreg.HKEY_CURRENT_USER,r'SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe') as key:
                chrome_path, _ = winreg.QueryValueEx(key, None)
                return chrome_path
        except FileNotFoundError:
            try:
                with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r'SOFTWARE\Microsoft\Windows\CurrentVersion\App Paths\chrome.exe') as key:
                    chrome_path, _ = winreg.QueryValueEx(key, None)
                    return chrome_path
            except FileNotFoundError:
                return None

    def tb_login_btn(self):
        msg = ''
        chrome_path = self.get_chrome_path()
        if chrome_path:
            self.logger.info("已定位谷歌浏览器")
        else:
            self.logger.info("未找到谷歌浏览器安装位置")
            return
        try:
            hostname = socket.gethostname()
            ip_address = socket.gethostbyname(hostname)
            cdp_url = get_setting('CDPURL')
            try:
                # 尝试通过 CDP 端口访问浏览器实例
                response = requests.get(cdp_url)
                if response.status_code == 200:
                    msg = '已有目标浏览器实例运行，已连接,请登录'
            except requests.exceptions.RequestException:
                # 如果请求失败，说明没有浏览器实例在运行
                command = chrome_path + get_setting('START_CHROME')['PRDPATH']
                subprocess.Popen(command)
                time.sleep(5)
                msg = '已打开指定端口浏览器,请登录'
        except BaseException as e:
            msg = f'打开指定端口浏览器失败: {str(e)}'
        finally:
            self.logger.info(msg)

    def get_selected_ids(self):
        '''
        获取已选择的店铺,返回已选择的店铺id列表
        :return: 选中的店铺 ID 列表
        '''
        selected_ids = set()  # 使用 set 防止重复的 ID
        model = self.tb_list_view.model()

        # 递归检查节点状态，确保父子节点联动
        def check_node(item):
            # 如果是叶子节点（店铺）
            if not item.hasChildren():
                if item.checkState() == QtCore.Qt.Checked:
                    # 提取ID，跳过'√ '或 '× '前缀
                    item_text = item.text().split(' ')[1].split('.')[0]
                    selected_ids.add(int(item_text))  # 使用 set 自动去重
            else:
                # 如果是平台（父节点），检查它的选中状态
                for row in range(item.rowCount()):
                    child = item.child(row)
                    check_node(child)  # 递归检查子节点

                # 如果父节点本身是选中的，且子节点全部选中，也需要处理
                if item.checkState() == QtCore.Qt.Checked:
                    for row in range(item.rowCount()):
                        child = item.child(row)
                        check_node(child)  # 递归检查子节点，确保选中状态

        # 遍历所有顶层平台节点（第一层）
        for row in range(model.rowCount()):
            parent_item = model.item(row)
            check_node(parent_item)  # 检查父节点和子节点的状态

        return sorted(list(selected_ids))  # 转换回列表并排序

    def tb_start_btn(self):
        self.selected_ids = self.get_selected_ids()
        if len(self.selected_ids) < 1:
            QMessageBox.critical(self, "错误", "请至少勾选一个店铺！")
            return
        # 创建弹窗
        dialog = TimerDialog(self)
        if dialog.exec_():  # 如果点击了确定
            start_time = dialog.get_start_time()  # 获取定时任务开始时间
            interval = dialog.get_interval()  # 获取定时任务运行间隔（分钟）
            self.min_wait_time,self.max_wait_time = dialog.get_wait_time()  # 获取最小等待时间# 获取最大等待时间
            if self.min_wait_time > self.max_wait_time:
                QMessageBox.critical(self, "错误",'最小随机等待时间不能大于最大随机等待时间!')
                return

            # 转换为毫秒
            interval_ms = interval * 60 * 1000

            # 设置定时任务开始时间
            self.tb_timer_start_time = start_time
            self.tb_interval = interval

            self.selected_browser = dialog.get_selected_browser()

            # 启动定时任务
            self.start_timer(interval_ms)

    def start_timer(self, interval_ms):
        # 获取当前时间与定时任务开始时间的时间差
        current_time = QDateTime.currentDateTime()
        time_diff = self.tb_timer_start_time.toMSecsSinceEpoch() - current_time.toMSecsSinceEpoch()

        # 如果定时任务的启动时间在未来，使用 QTimer.singleShot 延迟到指定时间
        if time_diff > 0:
            QTimer.singleShot(time_diff, self.start_worker)
            self.logger.info(
                f'定时任务已设置,启动时间:{self.tb_timer_start_time.toString("yyyy-MM-dd HH:mm:ss")},运行间隔:{self.tb_interval}分钟')
        else:
            # 如果指定时间已经过去，则立即执行
            self.start_worker()
            self.logger.info(
                f'定时任务已设置,启动时间{self.tb_timer_start_time.toString("yyyy-MM-dd HH:mm:ss")}在过去,立即执行任务,运行间隔:{self.tb_interval}分钟')

        # 启动定时器，在指定的间隔时间后定期执行任务
        if not self.timer:
            self.timer = QTimer()
            self.timer.timeout.connect(self.start_worker)

        # 启动定时器
        self.timer.start(interval_ms)

    def start_worker(self):
        self.logger.info('自动获取任务已开始...')
        self.work = WorkerThread(self.page, self.logger,self.selected_browser,self.selected_ids,self.min_wait_time,self.max_wait_time,self.againbtn.isChecked(),self.login_data)
        self.work.finished.connect(self.on_finished)  # 连接信号
        self.work.upLog.connect(self.up_log)
        self.work.status_signal.connect(self.update_status)
        self.work.start()

        # 计算下次执行时间
        if self.tb_interval:
            zint = self.tb_interval * 60 * 1000
        else:
            zint = get_setting('TASK_INTERVAL')['TB_TASK'] * 1000  # 获取间隔（毫秒）
        self.tb_timer_start_time = self.tb_timer_start_time.addMSecs(zint)# 更新计算下次执行时间
        self.next_txt = '下次运行时间:' + self.tb_timer_start_time.toString("yyyy-MM-dd HH:mm:ss")
        self.tb_next_date.setText(self.next_txt)  # 更新标签显示时间

    def tb_stop_btn(self):
        if self.timer is not None:
            self.timer.stop()  # 停止定时器
            self.timer = None  # 重置定时器
            self.tb_next_date.setText('下次运行时间:未设置')
            self.logger.info('定时任务已停止')

    def tb_save_btn(self):
        if self.store_names is None:
            self.logger.info('未获取最新店铺数据!')
        else:
            # 弹出店铺选择对话框
            dialog = StoreSelectionDialog(self.store_names,parent=self)
            if dialog.exec_() == QDialog.Accepted:
                store_id, store_name = dialog.get_selected_store()
                try:
                    playwright = sync_playwright().start()
                    browser = playwright.chromium.connect_over_cdp(get_setting('CDPURL'))
                    context = browser.contexts[0]
                    page = context.pages[0]
                    base_dir = os.getcwd()
                    auto_cookie_folder = os.path.join(base_dir, 'cookies/tb')
                    if not os.path.exists(auto_cookie_folder):
                        os.makedirs(auto_cookie_folder)
                    cookie_path = auto_cookie_folder + '/' + str(store_id) + '.json'
                    # 获取 cookies
                    context.storage_state(path=cookie_path)
                    msg = '店铺:' + store_name + '登录数据保存成功'
                    page.close()
                    context.close()
                    browser.close()
                except BaseException as e:
                    msg = '保存店铺:' + store_name + '登录数据失败,详情:' + str(e)
                finally:
                    if 'playwright' in locals() and playwright:
                        playwright.stop()  # 停止 Playwright
                    self.logger.info(msg)

    def tb_task_data_btn(self):
        connection = pymysql.connect(
            host=self.dburl,
            user='root',
            password='123',
            database='ecadb'
        )
        cursor = connection.cursor()
        try:
            ip_address = get_user_ip()
            # 获取最近7天的任务
            select_sql = """
                    SELECT task_name, task_status 
                    FROM download_task 
                    WHERE task_name >= %s and ip_address = %s
                """
            # 获取当前日期的前7天
            seven_days_ago = (datetime.datetime.now() - datetime.timedelta(days=7)).strftime('%Y-%m-%d')
            cursor.execute(select_sql, (seven_days_ago,ip_address))
            results = cursor.fetchall()
            # 对结果按日期排序，确保先显示日期靠后的任务
            results_sorted = sorted(results, key=lambda x: x[0],reverse=True)
            # 处理结果并返回
            tasks = {}
            for result in results_sorted:
                task_name = str(result[0])
                task_status = json.loads(result[1])
                tasks[task_name] = task_status
        except Exception as e:
            self.logger.error(f"查询任务状态错误: {e}")
            raise

        # 创建并显示弹窗
        tree_dialog = TreeDialog(tasks, self)
        tree_dialog.exec_()

    def on_finished(self):
        self.logger.info(f'-本次自动获取任务已结束')

#======================================   企微审批数据下载 =======================================
    def qw_in_data(self):
        file_path, _ = QFileDialog.getOpenFileName(
            self, "请选择要导入的 Excel",
            os.path.expanduser("~"),
            "Excel 文件 (*.xlsx)"
        )
        if not file_path:
            return
        try:
            wb = openpyxl.load_workbook(file_path, data_only=True)
            ws = wb.worksheets[0]
        except Exception as e:
            print(f'读取 Excel 失败：{e}')
            return 0, f'读取 Excel 失败：{e}'

        names = [str(cell.value).strip() for cell in ws['A'][1:] if cell.value and str(cell.value).strip()]

        if not names:
            QMessageBox.critical(self, 'Excel 中没有姓名数据')
            return 0
        count, err = self.qwdb.import_persons(names)
        if err:
            QMessageBox.critical(self, '导入失败', err)
        else:
            self.update_status( f'导入成功,已导入 {count} 条人员姓名')

    def qw_out_data(self):
        save_dir = QFileDialog.getExistingDirectory(self, "请选择保存路径", os.path.expanduser("~"))
        if not save_dir:
            return
        save_path = os.path.join(save_dir, "企微审批数据获取人员清单.xlsx")

        dataDict = self.qwdb.query_person_names()
        wb = openpyxl.Workbook()
        ws = wb.active
        ws.title = '人员清单'
        ws.append(['人员姓名'])  # 表头
        for name in dataDict['data']:
            ws.append([name])  # 数据行
        wb.save(save_path)
        self.update_status(f'文件已保存到：{save_path}（共 {len(dataDict["data"])} 条）')

    def qw_create_task(self):
        today = datetime.date.today()

        # 1. 先查今天是否已经生成过
        exists, count_exists = self.qwdb.today_tasks_exist(today)  # 返回值见下方
        if exists:
            # 2. 弹窗让用户选择
            box = QMessageBox(self)
            box.setWindowTitle('提示')
            box.setIcon(QMessageBox.Information)
            box.setText(f'今日已为 {count_exists} 位人员生成过数据，是否重新生成全部数据？')
            btn_regen = box.addButton('重新生成', QMessageBox.AcceptRole)
            btn_show = box.addButton('显示已有', QMessageBox.RejectRole)
            box.setDefaultButton(btn_show)
            box.exec_()

            clicked = box.clickedButton()
            if clicked == btn_show:
                # 3A. 直接查询已有数据并刷新
                rows, err = self.qwdb.query_tasks(start_date=today, end_date=today)
                if err:
                    QMessageBox.warning(self, '提示', f'读取已有数据失败：{err}')
                else:
                    self._refresh_qw_table(rows)
                return  # 不再往下走
            # 3B. 如果点了“重新生成”，继续执行下面逻辑

        # 4. 原有逻辑：重新创建
        count, err = self.qwdb.create_today_tasks()
        if err:
            QMessageBox.critical(self, '创建失败', err)
            return

        # 5. 创建成功 → 查询刷新
        rows, err2 = self.qwdb.query_tasks(start_date=today, end_date=today)
        if err2:
            QMessageBox.warning(self, '提示', f'任务已创建，但刷新表格失败：{err2}')
        else:
            self._refresh_qw_table(rows)

        QMessageBox.information(self, '完成', f'已为 {count} 位人员创建今日任务')

    def _refresh_qw_table(self, rows):
        """
        rows: List[Tuple]  (user_name, task_date, task_run_time, task_count, task_flag)
        """
        table = self.qw_table
        table.clearContents()
        table.setRowCount(len(rows))
        table.setColumnCount(5)
        table.setHorizontalHeaderLabels(['人员姓名', '日期', '状态', '待处理总数', '运行时间'])

        for r, (name, date, run_time, cnt, flag) in enumerate(rows):
            table.setItem(r, 0, QTableWidgetItem(str(name)))
            table.setItem(r, 1, QTableWidgetItem(str(date)))
            table.setItem(r, 2, QTableWidgetItem(str(flag)))
            table.setItem(r, 3, QTableWidgetItem(str(cnt)))
            table.setItem(r, 4, QTableWidgetItem(str(run_time)))
        table.resizeColumnsToContents()

    def qw_open_brower(self):
        msg = ''
        chrome_path = self.get_chrome_path()
        if not chrome_path:
            QMessageBox.critical(self, '打开浏览器失败', "未找到谷歌浏览器安装位置")
            return
        try:
            hostname = socket.gethostname()
            ip_address = socket.gethostbyname(hostname)
            cdp_url = get_setting('CDPURL')
            try:
                # 尝试通过 CDP 端口访问浏览器实例
                response = requests.get(cdp_url)
                if response.status_code == 200:
                    msg = '已有目标浏览器实例运行，已连接,请登录'
            except requests.exceptions.RequestException:
                # 如果请求失败，说明没有浏览器实例在运行
                qw_login_url = get_setting('TASK_URL')['QWLOGINURL']
                command = chrome_path + get_setting('START_CHROME')['PRDPATH'] + ' ' + qw_login_url
                subprocess.Popen(command)
                time.sleep(5)
                msg = '已打开指定端口浏览器,请登录'
        except BaseException as e:
            msg = f'打开指定端口浏览器失败: {str(e)}'
        finally:
            QMessageBox.information(self, '信息', msg)

    def qw_start(self):
        today = datetime.date.today()
        rows, err = self.qwdb.query_tasks(start_date=today,end_date=today,task_flag='false')
        if len(rows) == 0:
            QMessageBox.warning(self, '提示', f'{today}没有待处理任务')
            return

        self.qw_work = WorkerThreadQw(rows)
        self.qw_work.qwFinished.connect(self.qw_on_finished)  # 连接信号
        self.qw_work.upQwLog.connect(self.qw_up_log)
        self.qw_work.status_signal.connect(self.update_status)
        self.qw_work.save_download_status_signal.connect(self.save_download_status)
        self.qw_work.save_task_info_signal.connect(self.save_task_info)
        self.qw_work.start()

    def qw_to_excel(self):
        folder = QFileDialog.getExistingDirectory(self, "请选择保存目录")
        if not folder:
            return

        file_path = os.path.join(folder, "人员单据审核时长清单.xlsx")

        # 2. 取数据
        rows, err = self.qwdb.query_all_task_info()
        if err:
            QMessageBox.critical(self, "导出失败", f"查询失败：{err}")
            return
        if not rows:
            QMessageBox.information(self, "提示", "当前无数据可导出")
            return

        # 3. 用 openpyxl 写 Excel
        try:
            wb = Workbook()
            ws = wb.active
            ws.title = "单据时长清单"

            # 表头
            headers = ["人员姓名", "审批编号", "单据名称",
                       "提交时间", "单据最后下载时间", "持有时长(小时)", "单据首次下载时间"]
            ws.append(headers)

            # 数据行
            for r in rows:
                ws.append([
                    r["user_name"],
                    r["task_id"],
                    r["task_name"],
                    str(r["task_add_time"] or ""),
                    str(r["task_get_time"] or ""),
                    r["task_sum"],
                    str(r["task_first_time"] or "")
                ])

            wb.save(file_path)
            QMessageBox.information(self, "完成", f"已导出到：\n{file_path}")
        except Exception as e:
            QMessageBox.critical(self, "导出失败", str(e))

    def save_download_status(self,data:dict):
        self.qwdb.save_download_status(user_name=data['user_name'],task_count=data['task_count'])

    def save_task_info(self,datas:list):
        self.qwdb.save_task_info(datas)
        # 2. 重新查询今天数据
        today = datetime.date.today()
        rows, err = self.qwdb.query_tasks(start_date=today, end_date=today)
        if err:
            QMessageBox.warning(self, '提示', f'刷新表格失败：{err}')
            return

        # 3. 刷新界面
        self._refresh_qw_table(rows)

    def qw_up_log(self, message):
        self.qw_log.append(message)

    def qw_on_finished(self, message):
        self.qw_log.append(message)

def main():
    # app = QApplication(sys.argv)
    # app.setAttribute(Qt.AA_EnableHighDpiScaling)
    # import warnings
    # warnings.filterwarnings('ignore')
    #
    # window = SimpleApp()
    # window.show()
    # sys.exit(app.exec_())
    app = QApplication(sys.argv)
    app.setAttribute(Qt.AA_EnableHighDpiScaling)
    login = LoginDialog()
    if login.exec_() == QDialog.Accepted:
        window = SimpleApp()
        window.show()
        sys.exit(app.exec_())

if __name__ == "__main__":
    main()
